//
// Created on 2025/7/23.
//
// Node APIs are not fully supported. To solve the compilation error of the interface cannot be found,
// please include "napi/native_api.h".

#include "native/file_stream.h"
#include <cassert>
#include <cerrno>
#include <stdexcept>
#include <unistd.h>


FileStream::FileStream(std::string path, FileMode mode, FileAccess access, FileShare share, size_t bufsize)
    : bufsize_(bufsize), read_buffer_pos_(0), read_buffer_size_(0), write_buffer_count_(0) {
    handle_ = std::make_unique<SafeFileHandle>(path, mode, access, share);
    buff_ = std::make_unique<char[]>(bufsize_);
}

bool FileStream::canRead() const noexcept { return !isClosed() && handle_->canRead(); }

bool FileStream::canWrite() const noexcept { return !isClosed() && handle_->canWrite(); }

bool FileStream::canSeek() const noexcept { return !isClosed() && handle_->canSeek(); }

size_t FileStream::position() const {
    if (isClosed())
        StreamError::ThrowClosed();
    else if (!canSeek())
        StreamError::ThrowNotSupported_UnseekableStream();
    return handle_->position() + read_buffer_pos_ - read_buffer_size_ + write_buffer_count_;
}

size_t FileStream::length() const {
    if (isClosed())
        StreamError::ThrowClosed();
    else if (!canSeek())
        StreamError::ThrowNotSupported_UnseekableStream();

    size_t len = handle_->length();
    size_t pos = handle_->position();

    if (write_buffer_count_ > 0 && ((pos + write_buffer_count_) > len)) {
        len = write_buffer_count_ + pos;
    }
    return len;
}

void FileStream::setLength(size_t length) {
    if (isClosed())
        StreamError::ThrowClosed();
    if (!canSeek())
        StreamError::ThrowNotSupported_UnseekableStream();
    if (!canWrite())
        StreamError::ThrowNotSupported_UnwritableStream();
    if (length == static_cast<size_t>(-1)) {
        throw std::invalid_argument("Invalid file length");
    }

    flush();

    handle_->setLength(length);
}

size_t FileStream::seek(long offset, SeekOrigin origin) {
    if (isClosed())
        StreamError::ThrowClosed();
    if (!canSeek())
        StreamError::ThrowNotSupported_UnseekableStream();


    if (write_buffer_count_ == 0 && read_buffer_size_ == 0) {
        return handle_->seek(offset, static_cast<int>(origin));
    }

    if (write_buffer_count_ > 0) {
        flushWriteBuffer();
        return handle_->seek(offset, static_cast<int>(origin));
    }

    if (read_buffer_size_ - read_buffer_pos_ > 0 && origin == SeekOrigin::Current) {
        offset -= (read_buffer_size_ - read_buffer_pos_);
    }

    long oldPos = position();

    long newPos = handle_->seek(offset, static_cast<int>(origin));

    long readPos = (newPos - (oldPos - read_buffer_pos_));

    if (0 <= readPos && readPos < read_buffer_size_) {
        read_buffer_pos_ = readPos;
        handle_->seek(read_buffer_size_ - read_buffer_pos_, static_cast<int>(origin));
    } else {
        read_buffer_pos_ = 0;
        read_buffer_size_ = 0;
    }
    return newPos;
}


long FileStream::read(void *buf, long offset, size_t len) {
    if (isClosed())
        StreamError::ThrowClosed();
    if (!canRead())
        StreamError::ThrowNotSupported_UnreadableStream();

    char *output = static_cast<char *>(buf) + offset;

    long n = read_buffer_size_ - read_buffer_pos_;
    if (n == 0) {
        if (write_buffer_count_ > 0)
            flushWriteBuffer();

        if (!canSeek() || len > bufsize_) {
            long read_bytes = handle_->read(output, len);
            read_buffer_pos_ = 0;
            read_buffer_size_ = 0;
            return read_bytes;
        }

        long read_bytes = handle_->read(buff_.get(), bufsize_);
        if (read_bytes == 0)
            return 0;

        read_buffer_size_ = read_bytes;
        read_buffer_pos_ = 0;
        n = read_bytes;
    }

    if (n > len) {
        n = len;
    }

    memcpy(output, buff_.get() + read_buffer_pos_, n);
    read_buffer_pos_ += n;

    if (canSeek()) {
        if (n < len) {
            long r = handle_->read(output + n, len - n);
            n += r;
            read_buffer_pos_ = 0;
            read_buffer_size_ = 0;
        }
    }

    return n;
}

void FileStream::write(const void *buf, long offset, size_t len) {
    if (isClosed())
        StreamError::ThrowClosed();
    if (!canWrite())
        StreamError::ThrowNotSupported_UnwritableStream();


    const char *input = static_cast<const char *>(buf) + offset;

    if (write_buffer_count_ == 0)
        purgeBeforeWrite(); // 重置读缓冲，有必要更新文件指针到正确位置

    if (write_buffer_count_ > 0) {
        long remaining = bufsize_ - write_buffer_count_;
        if (remaining > 0) {
            if (remaining >= len) {
                writeToBuffer(input, len);
                return;
            } else {
                writeToBuffer(input, remaining);
                input += remaining;
                len -= remaining;
            }
        }
        flushWriteBuffer();
    }

    if (len >= bufsize_) {
        handle_->write(input, len);
        return;
    } else if (len == 0)
        return;

    writeToBuffer(input, len);
}


void FileStream::flush() {
    if (isClosed())
        StreamError::ThrowClosed();

    if (write_buffer_count_ > 0) {
        flushWriteBuffer();
    } else if (read_buffer_size_ > 0) {
        if (canSeek()) {
            flushReadBuffer();
        }
    }
    handle_->flush();
}


void FileStream::purgeBeforeWrite() {
    if (read_buffer_size_ == read_buffer_pos_) {
        read_buffer_size_ = 0;
        read_buffer_pos_ = 0;
        return;
    }
    flushReadBuffer();
}

void FileStream::flushReadBuffer() {
    handle_->seek(read_buffer_pos_ - read_buffer_size_, SEEK_CUR);
    read_buffer_size_ = 0;
    read_buffer_pos_ = 0;
}

void FileStream::flushWriteBuffer() {
    if (write_buffer_count_ > 0) {
        handle_->write(buff_.get(), write_buffer_count_);

        write_buffer_count_ = 0;
    }
}

void FileStream::writeToBuffer(const void *data, size_t size) {
    assert(write_buffer_count_ + size <= bufsize_ && "Buffer overflow");

    memcpy(buff_.get() + write_buffer_count_, data, size);
    write_buffer_count_ += size;
}

void FileStream::onClose() {
    flush();
    handle_.reset();
}
